namespace ScrumSprintMonitor.Implementation.Configuration
{
	#region #using Directives

	using System;
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.Globalization;
	using System.IO;
	using System.Xml;
	using System.Xml.Schema;

	using Infrastructure;

	using Ninject.Core;
	using Ninject.Core.Parameters;

	#endregion

	public class XmlStreamBasedTeamInfoProvider : DataStreamObjectSerializer,
	                                              ITeamMemberInfoProvider
	{
		protected readonly IDictionary<string, ITeamMemberInformation> dictionary = new Dictionary<string, ITeamMemberInformation>();
		private readonly IKernel kernel;
		private readonly ILogger<XmlStreamBasedTeamInfoProvider> logger;
		private readonly IDataStreamProvider xsdStreamProvider;

		public XmlStreamBasedTeamInfoProvider(IKernel kernel, IDataStreamProvider xmlStreamProvider, IDataStreamProvider xsdStreamProvider,
		                                      ILogger<XmlStreamBasedTeamInfoProvider> logger)
			: base(xmlStreamProvider)
		{
			this.kernel = kernel;
			this.xsdStreamProvider = xsdStreamProvider;
			this.logger = logger;
		}

		protected override void ReadObjectFromStream()
		{
			GuardDisposed();

			this.dictionary.Clear();

			try
			{
				using (var stream = this.streamProvider.OpenStream(FileAccess.Read))
				{
					var settings = new XmlReaderSettings
					               	{
					               		Schemas = new XmlSchemaSet(),
					               		IgnoreWhitespace = false,
					               		IgnoreComments = true,
					               		ValidationType = ValidationType.Schema
					               	};
					if (this.xsdStreamProvider != null)
					{
						using (var xsdStream = this.xsdStreamProvider.OpenStream(FileAccess.Read))
						{
							using (var xsdReader = XmlReader.Create(xsdStream))
							{
								settings.Schemas.Add(null, xsdReader);
							}
						}
					}

					using (var reader = XmlReader.Create(stream, settings))
					{
						bool triedActivatingAvatarProviderStore = false;
						IAvatarProviderStore avatarProviderStore = null;

						var parameters = new Dictionary<string, object>
						                 	{
						                 		{"name", null},
						                 		{"unassigned", false},
						                 		{"displayAlias", string.Empty},
						                 		{"schedulerAlias", string.Empty},
						                 		{"domainName", string.Empty},
						                 		{"team", string.Empty},
						                 		{"timeZone", TimeZoneInfo.FindSystemTimeZoneById(TimeZoneInfo.Local.Id)},
						                 		{"workdayStart", new DateTime(2000, 1, 1, 9, 0, 0)},
						                 		{"workdayEnd", new DateTime(2000, 1, 1, 18, 0, 0)},
						                 		{"lunchHourStart", new DateTime(2000, 1, 1, 12, 0, 0)},
						                 		{"lunchHourEnd", new DateTime(2000, 1, 1, 13, 0, 0)},
						                 		{"hourlyProductivityFactor", 1.0},
						                 		{"dedicatedToSprint", true}
						                 	};
						reader.Read();
						reader.ReadStartElement("team-members");

						while (!reader.EOF)
						{
							if (reader.ReadToFollowing("team-member"))
							{
								parameters["name"] = null;
								parameters["unassigned"] = false;
								parameters["displayAlias"] = string.Empty;
								parameters["schedulerAlias"] = string.Empty;
								parameters["domainName"] = string.Empty;

								string avatarProviderId = null, avatarKey = null;
								IAvatar avatar = null;

								do
								{
									if (reader.NodeType == XmlNodeType.Element)
									{
										switch (reader.Name)
										{
											case "team-member":
											{
												parameters["name"] = reader.GetAttribute("name");

												string unassignedStr = reader.GetAttribute("unassigned");
												if (!string.IsNullOrEmpty(unassignedStr))
												{
													parameters["unassigned"] = Convert.ToBoolean(unassignedStr, CultureInfo.InvariantCulture);
												}
												break;
											}
											case "hourly-productivity-factor":
												parameters["hourlyProductivityFactor"] = Convert.ToDouble(reader.ReadElementContentAsString(), CultureInfo.InvariantCulture);
												break;
											case "dedicated-to-sprint":
												parameters["dedicatedToSprint"] = reader.ReadElementContentAsBoolean();
												break;
											case "display-alias":
												parameters["displayAlias"] = reader.ReadString();
												break;
											case "scheduler-alias":
												parameters["schedulerAlias"] = reader.ReadString();
												break;
											case "domain-name":
												parameters["domainName"] = reader.ReadString();
												break;
											case "team":
												parameters["team"] = reader.ReadString();
												break;
											case "time-zone":
												parameters["timeZone"] = TimeZoneInfo.FindSystemTimeZoneById(reader.ReadString());
												break;
											case "workday":
												reader.ReadToDescendant("start");
												parameters["workdayStart"] = DateTime.Parse(reader.ReadElementContentAsString(), CultureInfo.InvariantCulture);
												reader.ReadStartElement("end");
												parameters["workdayEnd"] = DateTime.Parse(reader.ReadContentAsString(), CultureInfo.InvariantCulture);
												reader.ReadEndElement();
												break;
											case "lunch":
												reader.ReadToDescendant("start");
												parameters["lunchHourStart"] = DateTime.Parse(reader.ReadElementContentAsString(), CultureInfo.InvariantCulture);
												reader.ReadStartElement("end");
												parameters["lunchHourEnd"] = DateTime.Parse(reader.ReadContentAsString(), CultureInfo.InvariantCulture);
												reader.ReadEndElement();
												break;
											case "avatar":
												avatarProviderId = reader.GetAttribute("provider");
												avatarKey = reader.GetAttribute("key");
												break;
										}
									}

									reader.Read();
								} while (reader.Depth == 2);

								if (!string.IsNullOrEmpty(avatarProviderId) && !string.IsNullOrEmpty(avatarKey))
								{
									if (avatarProviderStore == null && !triedActivatingAvatarProviderStore)
									{
										try
										{
											avatarProviderStore = this.kernel.Get<IAvatarProviderStore>();
										}
										catch (ActivationException)
										{
											triedActivatingAvatarProviderStore = true;
										}
									}

									if (avatarProviderStore != null)
									{
										var provider = avatarProviderStore.GetProvider(avatarProviderId);
										if (provider != null)
										{
											avatar = provider.GetAvatar(avatarKey);
										}
									}
								}

								var teamMemberInfo = this.kernel.Get<ITeamMemberInformation>(With.Parameters.ConstructorArguments(parameters));
								if (avatar != null)
								{
									teamMemberInfo.Avatar = avatar;
								}

								string key = (teamMemberInfo.Unassigned ? string.Format("unassigned:{0}", teamMemberInfo.Team) : teamMemberInfo.Name);

								this.dictionary.Add(key, teamMemberInfo);
							}
						}
					}
				}
			}
			catch (IOException e)
			{
				this.logger.Warn(e, "Exception detected while reading team information configuration.");

				throw;
			}

			AsyncRaisePropertyChanged(null); // This may get called from a non-UI thread
		}

		#region Implementation of ITeamMemberInfoProvider

		public ITeamMemberInformation GetTeamMemberInformation(string teamMemberName)
		{
			if (string.IsNullOrEmpty(teamMemberName))
			{
				throw new ArgumentNullException("teamMemberName");
			}

			EnsureObjectIsRead();

			ITeamMemberInformation info;
			if (this.dictionary.TryGetValue(teamMemberName, out info))
			{
				return info;
			}

			return null;
		}

		public virtual bool IsReadOnly
		{
			[DebuggerStepThrough]
			get { return true; }
		}

		[DebuggerStepThrough]
		public virtual void Save()
		{
			throw new NotSupportedException();
		}

		public IEnumerable<ITeamMemberInformation> TeamMembers
		{
			get
			{
				EnsureObjectIsRead();

				var list = new List<ITeamMemberInformation>(this.dictionary.Values);

				return list;
			}
		}

		#endregion
	}
}